using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using ValidationFramework.Extensions;

namespace ValidationFramework.Reflection
{
    /// <summary>
    /// A <see cref="AutoKeyDictionary{TKey,TItem}"/>  of <see cref="Rule"/>s.
    /// </summary>
#if (!SILVERLIGHT)
    [Serializable]
#endif
    public sealed class RuleCollection<T> : IEnumerable<T> where T : RuleBase
    {

        private readonly List<T> ruleList;
        private readonly List<T> rulesWithoutRuleSetList;
        private readonly Dictionary<string, IList<T>> ruleDictionary;

        #region Constructors

        /// <summary>
        /// Initialize a new instance of the <see cref="RuleCollection"/> class.
        /// </summary>
        /// <param name="infoDescriptor">The <see cref="InfoDescriptor"/> this <see cref="RuleCollection"/> belongs to.</param>
        internal RuleCollection()
        {
            ruleDictionary = new Dictionary<string, IList<T>>();
            ruleList = new List<T>();
            rulesWithoutRuleSetList = new List<T>();
        }

        #endregion


        #region Properties

        public int Count
        {
            get
            {
                return ruleList.Count;
            }
        }
        #endregion


        #region Methods
        /// <inheritdoc />
        /// <exception cref="ArgumentNullException"><paramref name="item"/> is null.</exception> 
        /// <exception cref="ArgumentException">An element with the same key already exists in the <see cref="AutoKeyDictionary{TKey,TItem}"/>.</exception>
        public void Add(T item, string ruleSet)
        {
            Guard.ArgumentNotNull(item, "item");

            ruleSet = ruleSet.ToUpperIgnoreNull();
            CheckForDuplicate(item, ruleSet);
            if (ruleSet == null)
            {
                rulesWithoutRuleSetList.Add(item);
            }
            else
            {
                ruleSet = ruleSet.ToUpper(CultureInfo.InvariantCulture);
                IList<T> rules;
                if (!ruleDictionary.TryGetValue(ruleSet, out rules))
                {
                    rules = new List<T>();
                    ruleDictionary.Add(ruleSet, rules);
                }
                rules.Add(item);
            }
            ruleList.Add(item);
        }

        internal void Merge(RuleCollection<T> ruleCollection)
        {
            foreach (var keyValuePair in ruleDictionary)
            {
                var ruleSet = keyValuePair.Key;
                foreach (var rule in keyValuePair.Value)
                {
                    if (!IsDuplicate(rule, ruleSet))
                    {
                        Add(rule, ruleSet);
                    }
                }
            }

            foreach (var rule in ruleCollection.ruleList)
            {
                if (!IsDuplicate(rule, null))
                {
                    Add(rule, null);
                }
            }
        }

        internal bool TryGetRulesForRuleSet(string ruleSet, out IList<T> rules)
        {
            if (ruleSet == null)
            {
                if (ruleList.Count == 0)
                {
                    rules = null;
                    return false;
                }
                else
                {
                    rules = new List<T>(ruleList);
                    return true;
                }
            }
            else
            {
                rules = null;
                ruleSet = ruleSet.ToUpper();
                IList<T> tempRules;
                var value = ruleDictionary.TryGetValue(ruleSet, out tempRules);
                if (value)
                {
                    rules = tempRules;
                }
                return value;
            }
        }

        public string GetRuleSet(T rule)
        {
            foreach (var keyValuePair in ruleDictionary)
            {
                foreach (var rule1 in keyValuePair.Value)
                {
                    if (rule1 == rule)
                    {
                        return keyValuePair.Key;
                    }
                }
            }
            foreach (var rule1 in ruleList)
            {
                if (rule1 == rule)
                {
                    return null;
                }
            }
            throw new ArgumentException("Rule not in RuleCollection.");
        }

        /// <inheritdoc />
        public void Remove(T rule)
        {
            var ruleSet = GetRuleSet(rule);
            if (ruleSet == null)
            {
                rulesWithoutRuleSetList.Remove(rule);
            }
            else
            {
                ruleDictionary[ruleSet].Remove(rule);
            }
            ruleList.Remove(rule);
        }

        /// <inheritdoc />
        public void Clear()
        {
            rulesWithoutRuleSetList.Clear();
            ruleDictionary.Clear();
            ruleList.Clear();
        }


        private void CheckForDuplicate(T rule, string ruleSet)
        {
            if (IsDuplicate(rule, ruleSet))
            {
                throw new ArgumentException(string.Format("An equivalent rule already exists in collection. RuleInterpretation : '{0}'", rule.RuleInterpretation), "rule");
            }
        }



        private bool IsDuplicate(T rule, string ruleSet)
        {
            //Store temp values for the new ruleSet and Type to save them being accessed for each iteration of the loop.
            var ruleType = rule.GetType();
            IList<T> rules;
            if (ruleSet == null)
            {
                rules = rulesWithoutRuleSetList;
            }
            else
            {
                if (!ruleDictionary.TryGetValue(ruleSet, out rules))
                {
                    return false;
                }
            }
            for (var ruleIndex = 0; ruleIndex < rules.Count; ruleIndex++)
            {
                var existingRule = rules[ruleIndex];
                if ((existingRule == rule) || (existingRule.GetType() == ruleType))
                {
                    if (existingRule.IsEquivalent(rule))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        #endregion

        public T this[int index]
        {
            get
            {
                return ruleList[index];
            }
        }
        /// <inheritdoc/>
        public IEnumerator<T> GetEnumerator()
        {
            return ruleList.GetEnumerator();
        }
        /// <inheritdoc/>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}